package rov.ui.widget;


import rov.data.SensorData;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import java.awt.geom.Point2D;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue;


// graphical java swing component for graphing a value over time
public class Graph extends JPanel
{
    private String mName;
    private SensorData mSensor;
    private int mValIndex;
    private int[] mTicks;
    private int mMinVal;
    private int mMaxVal;
    private LinkedList<Line> mRefs;
    private LinkedList<Point2D.Double> mVals;
    private LinkedBlockingQueue<Point2D.Double> mNewVals;
    private Timer mRefresher;


    public Graph(String name, SensorData sensor, int valueIndex, int[] ticks, int updateMs, int minVal, int maxVal)
    {
        mName = name;
        mSensor = sensor;
        mValIndex = valueIndex;
        mTicks = new int[ticks.length];
        mMinVal = minVal;
        mMaxVal = maxVal;
        mRefs = new LinkedList<Line>();
        mVals = new LinkedList<Point2D.Double>();
        mNewVals = new LinkedBlockingQueue<Point2D.Double>();

        System.arraycopy(ticks, 0, mTicks, 0, ticks.length);

        setBorder(BorderFactory.createLineBorder(Color.black));
        setBackground(Color.white);
        //setBackground(Color.black);
        setPreferredSize(new Dimension(300, 100));

        mSensor.addListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                addValue(System.currentTimeMillis(), mSensor.getValue(mValIndex));
            }
        });

        mRefresher = new Timer(updateMs, new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                clearOldValues();
                addNewValues();
                repaint();
            }
        });

        mRefresher.start();
    }

    private void clearOldValues()
    {
        long now = System.currentTimeMillis();
        Iterator<Point2D.Double> iter = mVals.iterator();

        while (iter.hasNext())
        {
            Point2D.Double p = iter.next();

            //if ((now - p.getX()) > (mTicks[0] * 1000 - 500))
            if ((now - p.getX()) > (mTicks[0] * 1000) + 2000)
                iter.remove();
        }
    }

    private void addNewValues()
    {
        while (!mNewVals.isEmpty())
        {
            mVals.add(mNewVals.poll());
        }
    }

    public void addValue(long timeMs, double val)
    {
        mNewVals.add(new Point2D.Double(timeMs, val));
    }

    public void addLine(double val, Color c)
    {
        if (val < mMinVal || val > mMaxVal)
            throw new IllegalArgumentException("Graph line value invalid");

        mRefs.add(new Line((mMaxVal - val) / (mMaxVal - mMinVal), c));
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
        FontMetrics fm = g.getFontMetrics();
        int graphBorder = 5;
        int graphWidth = getWidth() - graphBorder * 4;
        int graphHeight = getHeight() - graphBorder * 2 - fm.getHeight() * 2;
        int graphBottom = fm.getHeight() + graphBorder + graphHeight;
        int spacing = (graphWidth - graphBorder * 2) / (mTicks.length - 1);
        Stroke stroke = g2.getStroke();

        g.setColor(Color.black);
        g.fillRect(graphBorder * 2, fm.getHeight() + graphBorder, graphWidth, graphHeight);

        for (Line l : mRefs)
        {
            int y = (int) (l.val * graphHeight) + fm.getHeight() + graphBorder;

            g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] { 10 }, 4.0f));
            g.setColor(l.color);
            g.drawLine(graphBorder * 2, y, graphBorder * 2 + graphWidth, y);
        }

        g2.setStroke(stroke);
        g.setColor(Color.black);
        //g.setColor(Color.white);
        g.drawString(mName, getWidth() / 2 - fm.stringWidth(mName) / 2, fm.getHeight());
        //g.drawRect(graphBorder * 2, fm.getHeight() + graphBorder, graphWidth, graphHeight);

        for (int i = 0; i < mTicks.length; i++)
            g.drawString(mTicks[i] + " s", spacing * i + graphBorder, graphBottom + fm.getHeight());

        long now = System.currentTimeMillis();
        Iterator<Point2D.Double> it = mVals.iterator();
        Point2D.Double last = null;

        Shape oldClip = g.getClip();
        g.setClip(graphBorder * 2, fm.getHeight() + graphBorder, graphWidth, graphHeight);

        g.setColor(Color.white);
        //g.setColor(Color.gray);

        if (it.hasNext())
            last = it.next();

        while (it.hasNext())
        {
            Point2D.Double curr = it.next();

            double val = mMaxVal - Math.min(Math.max(curr.getY(), mMinVal), mMaxVal);
            double lastVal = mMaxVal - Math.min(Math.max(last.getY(), mMinVal), mMaxVal);

            /* integral
            int x1 = graphBorder * 2 + graphWidth - (int) ((now - curr.getX()) / 1000.0 / mTicks[0] * graphWidth);
            int x2 =  graphBorder * 2 + graphWidth - (int) ((now - last.getX()) / 1000.0 / mTicks[0] * graphWidth);

            int y1 = (int) (val / (mMaxVal - mMinVal) * graphHeight) + fm.getHeight() + graphBorder;
            int y2 = (int) (lastVal / (mMaxVal - mMinVal) * graphHeight) + fm.getHeight() + graphBorder;

            g.fillPolygon(new int[] { x1, x1, x2, x2 }, new int [] { graphBottom, y1, y2, graphBottom }, 4);
            */

            // line
            g.drawLine(graphBorder * 2 + graphWidth - (int) ((now - curr.getX()) / 1000.0 / mTicks[0] * graphWidth), (int) (val / (mMaxVal - mMinVal) * graphHeight) + fm.getHeight() + graphBorder,
                       graphBorder * 2 + graphWidth - (int) ((now - last.getX()) / 1000.0 / mTicks[0] * graphWidth), (int) (lastVal / (mMaxVal - mMinVal) * graphHeight) + fm.getHeight() + graphBorder);

            // points
            //g.fillRect(graphBorder + graphWidth - (int) ((now - curr.getX()) / 1000.0 / mTicks[0] * graphWidth), (int) ((val - mMinVal) / (mMaxVal - mMinVal) * graphHeight) + fm.getHeight() + graphBorder, 2, 2);

            last = curr;
        }

        g.setClip(oldClip);
    }


    private static class Line
    {
        public double val;
        public Color color;


        public Line(double val, Color color)
        {
            this.val = val;
            this.color = color;
        }
    }
}
